package office.document.pdfbox;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import office.document.core.constant.enums.DocumentHeadLevelEnum;
import office.document.core.domain.DocumentHeadDO;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import wiki.xsx.core.pdf.doc.XEasyPdfDocument;
import wiki.xsx.core.pdf.doc.XEasyPdfDocumentBookmark;
import wiki.xsx.core.pdf.handler.XEasyPdfHandler;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * PDF文档大纲工具类
 * - 文档大纲解析，获取
 *
 * @author: zhanglu
 * @date: 2023/7/31 17:39
 * @modify: 修改人信息, 修改内容 2023/7/31 17:39
 */
@Slf4j
public class PdfOutlineUtil {

    /**
     * 子标题最大数量
     */
    public static final Integer SUB_HEAD_MAX_COUNT = 100;
    /**
     * 标题最大数量
     */
    public static final Integer HEAD_MAX_COUNT = 1000;

    /**
     * 获取文档书签
     *
     * @param pdfBytes pdf格式字节数据
     */
    public static DocumentHeadDO getDocumentBookmarkTree(byte[] pdfBytes) {
        Assert.isTrue(ObjectUtil.isNotEmpty(pdfBytes), "请输入有效的pdf文件");
        //初始化顶级大纲
        DocumentHeadDO head = new DocumentHeadDO();
        try {
            //加载pdf
            XEasyPdfDocument document = XEasyPdfHandler.Document.load(new ByteArrayInputStream(pdfBytes));
            //获取大纲
            XEasyPdfDocumentBookmark bookmark = document.bookmark();
            List<PDOutlineItem> bms = bookmark.getBookMark();
            head.setHeadLevel(DocumentHeadLevelEnum.H1);
            head.setChilds(new ArrayList<>());
            //顶级大纲找子集
            for (PDOutlineItem bm : bms) {
                DocumentHeadDO h = new DocumentHeadDO();
                h.setHeadLevel(DocumentHeadLevelEnum.H2);
                h.setText(bm.getTitle());
                head.getChilds().add(h);
                //若无子集则返回
                PDOutlineItem firstChild = bm.getFirstChild();
                if (firstChild == null) {
                    continue;
                }
                //存在子集，继续往下找
                loopBookmark(DocumentHeadLevelEnum.H3.getIndex(), h, firstChild);
            }
        } catch (Exception e) {
            log.error("获取pdf大纲异常", e);
        }
        return head;
    }

    /**
     * 获取文档书签
     *
     * @param root
     */
    public static List<DocumentHeadDO> getDocumentBookmarks(DocumentHeadDO root) {
        List<DocumentHeadDO> heads = new ArrayList<>();
        loopChilds(HEAD_MAX_COUNT, heads, root);
        System.out.println(JSONUtil.toJsonStr(heads));
        return heads;
    }

    /**
     * 找孩子
     *
     * @param count 递归次数限制
     * @param heads 节点
     * @param h     当前节点
     */
    public static void loopChilds(Integer count, List<DocumentHeadDO> heads, DocumentHeadDO h) {
        Assert.isTrue(count >= 0, "提取标题异常，子标题过多:{}", count);
        count--;
        heads.add(h);
        if (ObjectUtil.isEmpty(h.getChilds())) {
            return;
        }
        for (DocumentHeadDO child : h.getChilds()) {
            loopChilds(count, heads, child);
        }
        h.setChilds(null);
    }

    /**
     * 获取文档大纲
     *
     * @param pdfBytes pdf格式字节数组
     */
    public static List<DocumentHeadDO> getDocumentBookmarks(byte[] pdfBytes) {
        Assert.isTrue(ObjectUtil.isNotEmpty(pdfBytes), "请输入有效的pdf文件");
        DocumentHeadDO root = getDocumentBookmarkTree(pdfBytes);
        List<DocumentHeadDO> heads = getDocumentBookmarks(root);
        return heads;
    }

    /**
     * 找子集
     *
     * @param index 索引
     * @param root  根节点
     * @param head  当前节点
     */
    public static void loopBookmark(Integer index, DocumentHeadDO root, PDOutlineItem head) {
        if (ObjectUtil.isEmpty(root)) {
            return;
        }
        //初始化子集
        if (ObjectUtil.isEmpty(root.getChilds())) {
            root.setChilds(new ArrayList<>());
        }
        DocumentHeadDO h = new DocumentHeadDO();
        //获取子集等级
        h.setHeadLevel(DocumentHeadLevelEnum.getByIndex(index));
        h.setText(head.getTitle());
        root.getChilds().add(h);
        //获取子集节点
        PDOutlineItem firstChild = head.getFirstChild();
        //找孩子
        loopNext(SUB_HEAD_MAX_COUNT, index + 1, root, firstChild);
        //获取兄弟节点
        PDOutlineItem nextSibling = head.getNextSibling();
        //找兄弟
        loopNext(SUB_HEAD_MAX_COUNT, index, root, nextSibling);
    }

    /**
     * 找兄弟
     *
     * @param count 递归次数限制
     * @param index 索引
     * @param head  当前节点
     * @param next  兄弟节点
     */
    public static void loopNext(Integer count, Integer index, DocumentHeadDO head, PDOutlineItem next) {
        Assert.isTrue(count >= 0, "提取标题异常，子标题过多:{}", count);
        count--;
        if (ObjectUtil.isEmpty(next)) {
            return;
        }
        //初始化子集
        if (ObjectUtil.isEmpty(head.getChilds())) {
            head.setChilds(new ArrayList<>());
        }
        DocumentHeadDO h = new DocumentHeadDO();
        h.setHeadLevel(DocumentHeadLevelEnum.getByIndex(index));
        h.setText(next.getTitle());
        head.getChilds().add(h);
        //找孩子
        if (ObjectUtil.isNotEmpty(next.getFirstChild())) {
            loopNext(count, index + 1, h, next.getFirstChild());
        }
        //找兄弟
        loopNext(count, index, head, next.getNextSibling());
    }

}
